第1章 作用域是什么
- 问题1:变量储存在哪里?
- 问题2:程序需要时如何找到它们?
1.1 编译原理
JavaScript语言是“动态”或“解释执行”语言,但事实上是一门编译语言。但它不是提前编译的,编译结果也不能在分布式系统中移植。
传统编译语言流程中,程序在执行之前会经历三个步骤,统称为“编译”。
分词/词法分析(Tokenizing/Lexing)
将由字符组成的字符串分解成(对编程语言来说)有意义的代码块。
1
var a = 2;
上面这段程序会被分解成以下词法单元:var、a、=、2、;。
空格是否会被当做词法单元,取决于空格在这门语言中是否有意义。
解析/语法分析(Parsing)
将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的数。这个数被称作
抽象语法树
(Abstract Syntax Tree, AST)。1
var a = 2;
以上代码的抽象语法树如下所示:
- VariableDeclaration 顶级节点
- Identifier 子节点,值为a
- AssignmentExpression 子节点
- NumericLiteral 子节点,字为2
- VariableDeclaration 顶级节点
代码生成
将
AST
转换成可执行代码的过程。过程与语言、目标平台等相关。简单来说就是可以通过某种方法将
var a = 2;
的AST转化为一组机器指令。用来创建一个叫做a的变量(包括分配内存等),并将一个值存储在a中。
1.2 理解作用域
1.2.1 演员表
- 引擎:从头到尾负责整个JavaScript程序的编译和执行。
- 编译器:负责语法分析和代码生成等
- 作用域:负责收集并维护由所有声明的标识符(变量、函数)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
1.2.2 对话
var a = 2;
存在2个不同的声明。
1、编译器在编译时处理(
var a
):在当前作用域中声明一个变量(如果之前没有声明过)。1
2
3
4
5
6
7
8
9
10
11st=>start: Start
e=>end: End
op1=>operation: 分解成词法单元
op2=>operation: 解析成树结构AST
cond=>condition: 当前作用域存在变量a?
op3=>operation: 忽略此声明,继续编译
op4=>operation: 在当前作用域集合中声明新变量a
op5=>operation: 生成代码
st->op1->op2->cond
cond(yes)->op3->op5->e
cond(no)->op4->op5->e2、引擎在运行时处理(
a = 2
):在作用域中查找该变量,如果找到就对变量赋值。
1 | st=>start: Start |
1.2.3 LHS和RHS查询
L
和R
分别代表一个赋值操作的左侧和右侧,当变量出现在赋值操作的左侧时进行LHS
查询,出现在赋值操作的非左侧
时进行RHS
查询。
LHS查询(左侧):找到变量的容器本身,然后对其赋值
RHS查询(非左侧):查找某个变量的值,可以理解为
retrieve his source value
,即取到它的源值
1 | function foo(a) { |
上述代码共有1处LHS查询,3处RHS查询。
LHS查询有:
- 隐式的
a = 2
中,在2
被当做参数传递给foo(…)
函数时,需要对参数a
进行LHS查询
- 隐式的
RHS查询有:
最后一行
foo(...)
函数的调用需要对foo进行RHS查询console.log( a );
中对a
进行RHS查询console.log(...)
本身对console
对象进行RHS查询
1.3 作用域嵌套
遍历嵌套作用域链的规则:引擎从当前的执行作用域开始查找变量,如果找不到就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没有找到,查找过程都会停止。
1.4 异常
ReferenceError
和作用域判别失败相关,TypeError
表示作用域判别成功了,但是对结果的操作是非法或不合理的。
- RHS查询在作用域链中搜索不到所需的变量,引擎会抛出
ReferenceError
异常。 - 非严格模式下,LHS查询在作用域链中搜索不到所需的变量,全局作用域中会创建一个具有该名称的变量并返还给引擎。
- 严格模式下(ES5开始,禁止自动或隐式地创建全局变量),LHS查询失败会抛出
ReferenceError
异常 - 在RHS查询成功情况下,对变量进行不合理的操作,引擎会抛出
TypeError
异常。(比如对非函数类型的值进行函数调用,或者引用null或undefined类型的值中的属性)
1.5 小结
var a = 2
被分解成2个独立的步骤。
- 1、
var a
在其作用域中声明新变量 - 2、
a = 2
会LHS查询a,然后对其进行赋值